﻿using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using PCLCrypto;
using Bilibili.Model.Common;
using Bilibili.Model.User;
using Bilibili.Model.Resource;
using Bilibili.Common.Database;
using Bilibili.Common.Log;
using Bilibili.WebClient.Common;
using PCLUntils.Json;
using PCLUntils.Objects;
using Bilibili.Common.Api;
using System.Collections.Generic;
using Bilibili.Common;
using System.Web;

namespace Bilibili.WebClient.Api
{
    public sealed class UserClient : ClientBase
    {
        public Action<string> SsoCallback { get; set; }
        public async Task<ReturnModel<QRAuthInfo>> GetQRAuthInfo()
        {
            try
            {
                string result = await ApiClient.GetResults("https://passport.bilibili.com/x/passport-login/web/qrcode/generate");
                var jb = result.ToJObject();
                if (jb["code"].ToInt32() == 0)
                {
                    var m = jb["data"].ToObject<QRAuthInfo>();
                    return BuildSuccessResult(m);
                }
                else
                    return BuildFailedResult<QRAuthInfo>(jb["message"].ToString());
            }
            catch (Exception ex)
            {
                return BuildExceptionResult<QRAuthInfo>(ex);
            }
        }
        public async Task<ReturnModel<QrResult>> PollQRAuthInfo(string auth_code)
        {
            try
            {
                var result = await ApiClient.GetResults($"https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key={auth_code}");
                var obj = result.ToJObject();
                if (obj["code"].ToInt32() == 0)
                {
                    var code = obj["data"]["code"].ToInt32();
                    if (code == 0)
                    {
                        var cookies = ApiClient.GetCookies().GetCookies(new Uri("https://bilibili.com"));
                        var m = obj["data"].ToObject<QrResult>();
                        return BuildSuccessResult(m, cookies);
                    }
                    else if (code == 86038)
                        return BuildSuccessResult<QrResult>(null);
                    else
                        return BuildFailedResult<QrResult>();
                }
                else
                    return BuildFailedResult<QrResult>(obj["message"].ToString());
            }
            catch (Exception ex)
            {
                return BuildExceptionResult<QrResult>(ex);
            }
        }
        public async Task<ReturnModel<TokenInfo>> GetAccessKey()
        {
            try
            {
                var sign = $"api=http://link.acg.tv/forum.php{ApiProvider.Instance.AndroidKey.Secret}".ToMD5();
                var result = await ApiClient.GetResults($"https://passport.bilibili.com/login/app/third?appkey={ApiProvider.Instance.AndroidKey.Appkey}&api=http://link.acg.tv/forum.php&sign={sign}");
                var obj = result.ToJObject();
                if (obj["code"].ToInt32() == 0)
                {
                    var confirm_uri = obj["data"]["confirm_uri"].ToString();
                    var message = await ApiClient.GetRespone(confirm_uri);
                    message.Headers.TryGetValues("location", out IEnumerable<string> values);
                    var url = values?.FirstOrDefault();
                    var queries = HttpUtility.ParseQueryString(new Uri(url).Query);
                    var info = new TokenInfo
                    {
                        Mid = obj["data"]["user_info"]["mid"].ToInt64(),
                        Access_token = queries.Get("access_key")
                    };
                    return BuildSuccessResult(info);
                }
                else
                    return BuildFailedResult<TokenInfo>(obj["message"].ToString());
            }
            catch (Exception ex)
            {
                return BuildExceptionResult<TokenInfo>(ex);
            }
        }
        public async Task<ReturnModel> Follow(long uid)
        {
            try
            {
                DataInfo dataInfo = new DataInfo($"&act=1&fid={uid}&re_src=31", ApiProviderBase.Build5);
                string result = await ApiClient.PostResults("https://api.bilibili.com/x/relation/modify", dataInfo.ToString());
                var jb = result.ToJObject();
                if (jb.IsNotEmpty())
                {
                    if (jb["code"].ToInt32() == 0)
                        return BuildSuccessResult();
                    else
                        return BuildFailedResult("关注失败");
                }
                else
                    return BuildFailedResult();
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("Follow", ex);
                return BuildExceptionResult(ex);
            }
        }
        public async Task<ReturnModel> UnFollow(long uid)
        {
            try
            {
                DataInfo dataInfo = new DataInfo($"&act=2&fid={uid}&re_src=31", ApiProviderBase.Build5);
                string result = await ApiClient.PostResults("https://api.bilibili.com/x/relation/modify", dataInfo.ToString());
                var jb = result.ToJObject();
                if (jb.IsNotEmpty())
                {
                    if (jb["code"].ToInt32() == 0)
                        return BuildSuccessResult();
                    else
                        return BuildFailedResult("取消关注失败");
                }
                else
                    return BuildFailedResult();
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("UnFollow", ex);
                return BuildExceptionResult(ex);
            }
        }
        public void SetLoginSuccess(string access, string mid)
        {
            SettingHelper.AccessKey = access;
            SettingHelper.RefreshToken = access;
            SettingHelper.LoginExpires = DateTime.Now.AddSeconds(7200);
            if (long.TryParse(mid, out long userId))
                SettingHelper.UserID = userId;
            SSO(access);
        }
        public async Task<ReturnModel<UserAtList>> GetAtUser(int pageIndex)
        {
            try
            {
                ApiInfo apiInfo = new ApiInfo("http://api.live.bilibili.com/feed_svr/v1/feed_svr/get_user_info", $"&page={pageIndex}&pagesize=20&src=bilih5", true);
                var results = await ApiClient.GetResultsUTF8Encode(apiInfo);
                var userAtListModel = results.ParseObject<UserAtList>();
                return BuildSuccessResult(userAtListModel);
            }
            catch (Exception ex)
            {
                return BuildExceptionResult<UserAtList>(ex);
            }
        }
        public async Task<ReturnModel<UserInfo>> GetUserInfo(long userId,bool isSelf)
        {
            try
            {
                ApiInfo apiInfo = new ApiInfo("https://app.bilibili.com/x/v2/space", $"&vmid={userId}", true);
                string results = await ApiClient.GetResults(apiInfo);
                UserInfo model = results.ParseObject<UserInfo>();
                if (model.IsNotEmpty())
                {
                    if (isSelf)
                        SettingHelper.UserInfo = model;
                    return BuildSuccessResult(model);
                }
                else
                    return BuildFailedResult<UserInfo>();
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetUserInfo", ex);
                return BuildExceptionResult<UserInfo>(ex);
            }
            finally
            {
                SSO(ApiProvider.Instance.AccessKey);
            }
        }
        public async Task<LoginCallback> LoginV3(string username, string password)
        {
            try
            {
                var pwd = await EncryptedPassword(password);
                DataInfo dataInfo = new DataInfo($"actionKey=appkey&username={Uri.EscapeDataString(username)}&password={Uri.EscapeDataString(pwd)}&channel=bili&subid=1&permission=ALL",
                    ApiProviderBase.Build6, ApiProvider.Instance.AndroidTVKey);
                var results = await ApiClient.PostResults("https://passport.bilibili.com/x/passport-login/oauth2/login", dataInfo.ToString());
                var loginModel = results.ParseObject<AccountLogin>();
                return HandleLogin(loginModel);
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("LoginV3", ex);
                return new LoginCallback(LoginStatus.Error, "登录出现小问题,请重试");
            }
        }
        public async Task<ReturnModel<GetUserSubmit>> GetUserSubmit(string userId, int pageNum)
        {
            try
            {
                var uri = $"https://api.bilibili.com/x/space/arc/search?mid={userId}&ps=30&tid=0&pn={pageNum}&keyword=&order=pubdate&jsonp=jsonp";
                string results = await ApiClient.GetResults(uri);
                var model = results.ParseObject<GetUserSubmit>();
                return BuildSuccessResult(model);
            }
            catch (Exception ex)
            {
                return BuildExceptionResult<GetUserSubmit>(ex);
            }
        }
        public ReturnModel CheckAgainLogin(string access_key, string refresh_token, int expires, long userid)
        {
            try
            {
                SettingHelper.AccessKey = access_key;
                SettingHelper.RefreshToken = refresh_token;
                SettingHelper.LoginExpires = DateTime.Now.AddSeconds(expires);
                SettingHelper.UserID = userid;
                SSO(access_key);
                return BuildSuccessResult("登录成功");
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("GetCaptcha", ex);
                return BuildFailedResult("登录失败");
            }
        }
        public async Task<string> AuthBiliPlus()
        {
            try
            {
                if (!ApiProvider.Instance.IsLogin) return string.Empty;
                using HttpClient httpClient = new HttpClient();
                var rq = await httpClient.GetAsync(new Uri($"https://www.biliplus.com/login?act=savekey&mid={SettingHelper.UserID}&access_key={ApiProvider.Instance.AccessKey}&expire="));
                var setCookie = rq.Headers.GetValues("set-cookie")?.FirstOrDefault();
                StringBuilder stringBuilder = new StringBuilder();
                var matches = Regex.Matches(setCookie, "(.*?)=(.*?); ", RegexOptions.Singleline);
                foreach (Match match in matches)
                {
                    var key = match.Groups[1].Value.Replace("HttpOnly, ", "");
                    var value = match.Groups[2].Value;
                    if (key != "expires" && key != "Max-Age" && key != "path" && key != "domain")
                        stringBuilder.Append(match.Groups[0].Value.Replace("HttpOnly, ", ""));
                }
                SettingHelper.BiliPlusCookie = stringBuilder.ToString();
                return stringBuilder.ToString();
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("AuthBiliPlus", ex);
                return string.Empty;
            }
        }
        private async Task<string> EncryptedPassword(string passWord)
        {
            string base64String;
            try
            {
                string stringAsync = await ApiClient.GetResults("https://passport.bilibili.com/x/passport-login/web/key");
                JObject jObjects = JObject.Parse(stringAsync);
                string hash = jObjects["data"]["hash"].ToString();
                string key = jObjects["data"]["key"].ToString();
                string hashPass = string.Concat(hash, passWord);
                var collection = Regex.Match(key, "BEGIN PUBLIC KEY-----(?<key>[\\s\\S]+)-----END PUBLIC KEY");
                string publicKey = collection.Groups["key"].Value.Trim();
                byte[] numArray = Convert.FromBase64String(publicKey);
                var asymmetricKeyAlgorithmProvider = WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.RsaPkcs1);
                var cryptographicKey = asymmetricKeyAlgorithmProvider.ImportPublicKey(numArray, 0);
                var buffer = WinRTCrypto.CryptographicEngine.Encrypt(cryptographicKey, Encoding.UTF8.GetBytes(hashPass), null);
                base64String = Convert.ToBase64String(buffer);
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("EncryptedPassword", ex);
                base64String = passWord;
            }
            return base64String;
        }
        public async Task<ReturnModel<List<CountryItemModel>>> GetCountry()
        {
            try
            {
                ApiInfo apiInfo = new ApiInfo("https://passport.bilibili.com/x/passport-login/country", false, ApiProvider.Instance.LoginKey);
                var results = await ApiClient.GetResultsUTF8Encode(apiInfo);
                var obj = results.ToJObject();
                if (obj["code"].ToInt32() == 0)
                {
                    var countryItemModel = obj["data"]["list"].ParseObject<List<CountryItemModel>>();
                    return BuildSuccessResult(countryItemModel);
                }
                else
                    return BuildFailedResult<List<CountryItemModel>>();
            }
            catch (Exception ex)
            {
                return BuildExceptionResult<List<CountryItemModel>>(ex);
            }
        }
        public async Task<ReturnModel<ValueTuple<int, string, string>>> SendSMS(string cid, string phone)
        {
            try
            {
                var sessionId = Guid.NewGuid().ToString("N");
                DataInfo dataInfo = new DataInfo($"channel=bili&cid={cid}&tel={phone}&statistics={Current.STATISTICS}&login_session_id={sessionId}&buvid={Current.Instance.GetBuvid()}&local_id={Current.Instance.GetBuvid()}",
                    ApiProviderBase.Build6, ApiProvider.Instance.LoginKey);
                string stringAsync = await ApiClient.PostResults("https://passport.bilibili.com/x/passport-login/sms/send", dataInfo.ToString());
                var obj = stringAsync.ToJObject();
                if (obj["code"].ToInt32() == 0)
                {
                    var url = obj["data"]["recaptcha_url"]?.ToString();
                    var captchaKey = obj["data"]["captcha_key"]?.ToString();
                    if (url.IsEmpty())
                        return BuildSuccessResult((0, captchaKey, sessionId));
                    else
                        return BuildSuccessResult((1, url, sessionId));
                }
                else
                    return BuildFailedResult<ValueTuple<int, string, string>>();
            }
            catch (Exception ex)
            {
                return BuildExceptionResult<ValueTuple<int, string, string>>(ex);
            }
        }
        public async Task<ReturnModel<ValueTuple<int, string, string>>> SendSMS(string cid, string phone, string sessionId, GeetestInfo geetest)
        {
            try
            {
                string secCode = geetest != null ? Uri.EscapeDataString(geetest.SecCode) : string.Empty, validate = geetest != null ? geetest.Validate : string.Empty,
                    challenge = geetest != null ? geetest.Challenge : string.Empty, recaptchaToken = geetest != null ? geetest.RecaptchaToken : string.Empty;
                DataInfo dataInfo = new DataInfo($"channel=bili&cid={cid}&tel={phone}&statistics={Current.STATISTICS}&login_session_id={sessionId}&gee_seccode={secCode}&gee_validate={validate}&gee_challenge={challenge}&recaptcha_token={recaptchaToken}&buvid={Current.Instance.GetBuvid()}&local_id={Current.Instance.GetBuvid()}",
                    ApiProviderBase.Build6, ApiProvider.Instance.LoginKey);
                string stringAsync = await ApiClient.PostResults("https://passport.bilibili.com/x/passport-login/sms/send", dataInfo.ToString());
                var obj = stringAsync.ToJObject();
                if (obj["code"].ToInt32() == 0)
                {
                    var url = obj["data"]["recaptcha_url"]?.ToString();
                    var captchaKey = obj["data"]["captcha_key"]?.ToString();
                    if (url.IsEmpty())
                        return BuildSuccessResult((0, captchaKey, sessionId));
                    else
                        return BuildSuccessResult((1, url, sessionId));
                }
                else
                    return BuildFailedResult<ValueTuple<int, string, string>>();
            }
            catch (Exception ex)
            {
                return BuildExceptionResult<ValueTuple<int, string, string>>(ex);
            }
        }
        public async Task<LoginCallback> SmsLogin(string cid, string phone, string code, string session_id, string captcha_key)
        {
            try
            {
                DataInfo dataInfo = new DataInfo($"&cid={cid}&tel={phone}&login_session_id={session_id}&captcha_key={captcha_key}&code={code}",
                    ApiProviderBase.Build6, ApiProvider.Instance.LoginKey);
                var stringAsync = await ApiClient.PostResults("https://passport.bilibili.com/x/passport-login/login/sms", dataInfo.ToString());
                var loginModel = stringAsync.ParseObject<AccountLogin>();
                return HandleLogin(loginModel);
            }
            catch (Exception ex)
            {
                return new LoginCallback(LoginStatus.Fail, ex.Message);
            }
        }
        private LoginCallback HandleLogin(AccountLogin loginModel)
        {
            if (loginModel.IsNotEmpty())
            {
                if (loginModel.Code == 0)
                {
                    if (loginModel.Data.Status == 0)
                    {
                        SettingHelper.AccessKey = loginModel.Data.Token_info.Access_token;
                        SettingHelper.RefreshToken = loginModel.Data.Token_info.Refresh_token;
                        SettingHelper.LoginExpires = DateTime.Now.AddSeconds(loginModel.Data.Token_info.Expires_in);
                        SettingHelper.UserID = loginModel.Data.Token_info.Mid;
                        if (loginModel.Data.Cookie_info?.Cookies != null)
                            SettingHelper.UserInfoCookie = string.Join(";", loginModel.Data.Cookie_info.Cookies.Select(x => $"{x.Name}={x.Value}; "));
                        return new LoginCallback(LoginStatus.Success, "登录成功");
                    }
                    else if (loginModel.Data.Status == 1)
                        return new LoginCallback(LoginStatus.NeedValidate, loginModel.Data.Url, "本次登录需要安全验证");
                    else
                        return new LoginCallback(LoginStatus.Fail, loginModel.Message);
                }
                else if (loginModel.Code == -105)
                    return new LoginCallback(LoginStatus.NeedCaptcha, loginModel.Data.Url, "登录需要验证码");
                else
                    return new LoginCallback(LoginStatus.Fail, loginModel.Message);
            }
            else
                return new LoginCallback(LoginStatus.Fail);
        }
        private void SSO(string access_key)
        {
            try
            {
                var url = GetSignUrl($"https://passport.bilibili.com/api/login/sso?access_key={access_key}&appkey={ApiProvider.Instance.AndroidKey.Appkey}&build={ApiProviderBase.Build5}&gourl=https%3A%2F%2Faccount.bilibili.com%2Faccount%2Fhome&mobi_app=android&platform=android&ts={ApiProvider.Instance.TimeSpanSeconds}");
                SsoCallback?.Invoke(url);
            }
            catch (Exception ex)
            {
                LogManager.Instance.LogError("SSO", ex);
            }
        }
    }
}